/*
 * IncrMatchComboBox.java
 * Created on Aug 25, 2004
*/
package abdn.graph.growl.editor;

import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.util.*;
import javax.swing.*;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;

/**
 * IncrMatchComboBox.java
 * @author Rich Williams
 *
 * Combo box constructed with a list of strings that incrementally matches on
 * user input.
 * If editing is allowed, as text is typed in, the list is adjusted so that
 * only those items that match the typed characters remain in the list.  A
 * string not in the list can be entered.
 * If not editable, the first string matching the typed characters is highlighted.
 * Pressing enter completes the typed string, or a string can be selected
 * from the list.
 *
 */
public class IncrMatchComboBox extends JComboBox
{
  Vector strings;
  DefaultComboBoxModel model;
  boolean allowEdit;
  boolean inUpdate = false;
  boolean gotMatch = false; // typed string matches a list entry
  String match = null;

  public IncrMatchComboBox(Vector items, boolean allowEdit)
  {
    super();
    this.allowEdit = allowEdit;
    if( items != null )
    {
      Collections.sort(items);
      strings = (Vector)items.clone();
      setModel(model = new DefaultComboBoxModel(items));
    }
    else
    {
      strings = new Vector();
      setModel(model = new DefaultComboBoxModel());
    }
    setEditable(true);
    setSelectedIndex(-1);
    JTextField edt = (JTextField)getEditor().getEditorComponent();
    edt.getDocument().addDocumentListener(new DocumentListener(){
      public void changedUpdate(DocumentEvent arg0)
      {
        doUpdate();
      }

      public void insertUpdate(DocumentEvent arg0)
      {
        doUpdate();
      }

      public void removeUpdate(DocumentEvent arg0)
      {
        doUpdate();
      }
    });
    if( allowEdit == false )
    {
      edt.addKeyListener(new KeyAdapter(){
        public void keyPressed(KeyEvent e)
        {
          if (e.getKeyCode() == KeyEvent.VK_ENTER)
          {
            processEnter();
          }
        }
      });
    }
  }

  private void doUpdate()
  {
    if( !inUpdate )
    {
      Runnable updateList = new Runnable() {
        public void run() {
          inUpdate = true;
          String str = (String)getEditor().getItem();
          String sel = (String)getSelectedItem();
          gotMatch = str.equals(sel);
          if( sel == null || !gotMatch )
          {
            Vector partial = getPartialMatchList(str);
            if( allowEdit )
            {
              JTextField edt = (JTextField)getEditor().getEditorComponent();
              int pos = edt.getCaretPosition();
              model.removeAllElements();
              for(Iterator i = partial.iterator(); i.hasNext();)
              {
                model.addElement(i.next());
              }
              setSelectedItem(str);
              edt.select(0,0);
              edt.setCaretPosition(pos);
              if( partial.size() > 0 && str.length() > 0 )  // redisplay popup
              {
                setPopupVisible(false); // force it to be redrawn at current size
                setPopupVisible(true);
              }
              else  // don't show popup
              {
                setPopupVisible(false);
              }
            }
            else
            {
              if( partial.size() != 0 )
              {
                match = (String)partial.get(0);
              }
              else
              {
                getEditor().setItem(match);
              }
              if( match != null )
              {
                JTextField edt = (JTextField)getEditor().getEditorComponent();
                int pos = edt.getCaretPosition();
                setPopupVisible(true);
                setSelectedItem(match);
                getEditor().setItem(str);
                edt.select(0,0);
                if( pos > str.length() )
                {
                  pos = str.length();
                }
                edt.setCaretPosition(pos);
              }
              else
              {
                setPopupVisible(false);
              }
            }
          }
          if( str.length() > 0 && gotMatch && !allowEdit )
          {
            gotMatch = true;
          }
          inUpdate = false;
        }
      };
      SwingUtilities.invokeLater(updateList);
    }
  }

  private Vector getPartialMatchList(String current)
  {
    Vector partial = new Vector();
    for(Iterator i = strings.iterator(); i.hasNext();)
    {
      String s = (String)i.next();
      if( s.startsWith(current) )
      {
        partial.add(new String(s));
      }
    }
    return partial;
  }

  private void processEnter()
  {
    String str = (String)getEditor().getItem();
    Vector partial = getPartialMatchList(str);
    if( partial.size() > 0 )
    {
      match = (String)partial.get(0);
      gotMatch = true;
      setSelectedItem(match);
      getEditor().setItem(match);
      setPopupVisible(false);
    }
    else
    {
      setSelectedItem(str);
    }
  }

  public void replaceAll(Vector strings, boolean sorted)
  {
    if( !sorted )
    {
      Collections.sort(strings);
    }
    removeAllItems();
    for (Iterator i = strings.iterator(); i.hasNext();)
    {
      super.addItem(i.next());
    }

    this.strings = strings;
  }

  public void removeAllItems()
  {
    super.removeAllItems();
    strings.clear();
  }

  public void addItem(String s)
  {
    int index = Collections.binarySearch(strings, s);
    if( index < 0 )
    {
      index = -(index + 1);
    }
    insertItemAt(s, index);
  }

  public void insertItemAt(String s, int idx)
  {
    strings.insertElementAt(s, idx);
    super.insertItemAt(s, idx);
  }

  public void removeItem(String s)
  {
    int idx = strings.indexOf(s);
    if( idx != -1 )
    {
      removeItemAt(idx);
    }
  }

  public void removeItemAt(int idx)
  {
    super.removeItemAt(idx);
    strings.remove(idx);
  }

  public boolean gotMatch()
  {
    if( inUpdate )
    {
      return gotMatch;
    }
    String str = (String)getEditor().getItem();
    String sel = (String)getSelectedItem();
    return str.equals(sel);
  }

  public boolean getInUpdate()
  {
    return inUpdate;
  }

}
